/* EasyWay Game Engine
 * Copyright (C) 2006 Daniele Paggi.
 *  
 * Written by: 2006 Daniele Paggi<dshnt@hotmail.com>
 *   
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Library General Public License for more details.
 * 
 * You should have received a copy of the GNU Library General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
package org.easyway.objects.texture;

import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.io.InputStream;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;

import org.easyway.utils.ImageUtils;
import org.easyway.utils.Utility;
import org.lwjgl.BufferUtils;
import org.lwjgl.devil.IL;
import org.lwjgl.devil.ILU;

public class ImageData implements Serializable {
	/**
	 * generated version id
	 */
	private static final long serialVersionUID = -7893392091273534932L;

	/**
	 * the data of image
	 */
	protected transient ByteBuffer data;

	/**
	 * the width of image
	 */
	protected int width;

	/**
	 * the height of image
	 */
	protected int height;

	/**
	 * the width hardware of image
	 */
	protected int widthHW;

	/**
	 * the height hardware of image
	 */
	protected int heightHW;

	/**
	 * the internal format of image (RGB or RGBA)
	 */
	protected int format;

	protected boolean solid = true;

	public ImageData(TextureID texture, ByteBuffer bb) {
		data = bb;
		width = texture.width;
		widthHW = texture.widthHW;
		height = texture.height;
		heightHW = texture.heightHW;
		format = texture.format;
		solid = texture.solid;
	}

	/**
	 * crates a new instance of ImageData loading the image specified. If
	 * useAlpha is setted as true the image will use the RGBA format else will
	 * use the RGB format
	 * 
	 * @param path
	 *            the image path
	 * @param useAlpha
	 *            indicates if the image will use the internal format RGBA or
	 *            RGB
	 */
	public ImageData(String path, boolean useAlpha) {

		// numberOfByetes = 3 or 4
		// format = RGB or RGBA
		int numberOfBytes = ((format = (useAlpha ? ImageType.RGBA
				: ImageType.RGB)) == ImageType.RGB) ? 3 : 4;

		// convert the PATH as InputStream
		InputStream is = null;
		{
			if (path.startsWith("/"))
				path = path.substring(1);
			int index;
			// path = path.replaceAll("\\", "/");
			while ((index = path.indexOf("\\")) != -1)
				path = path.substring(0, index) + '/'
						+ path.substring(index + 1);
			try {
				is = Thread.currentThread().getContextClassLoader()
					.getResource(path).openStream();
			} catch (Exception e) {
				Utility.error("Image " + path + " was not found!", e);
				width = height = -1;
				data = null;
				return;
			}
		}
		assert is != null;
		int type = ImageUtils.getType(path);
		// load the data from the file
		IntBuffer scratch = BufferUtils.createIntBuffer(1);

		// create image in DevIL and bind it
		IL.ilGenImages(scratch);
		IL.ilBindImage(scratch.get(0));

		try {
			IL.ilLoadFromStream(is, type);
			is.close();
		} catch (Exception e) {
			Utility
				.error("Error to load Image", "ImageData(String,boolean)", e);
		}

		// convert data as byte
		IL.ilConvertImage(format, IL.IL_BYTE);

		// need to flip the image?
		if (type == IL.IL_BMP) {
			ILU.iluFlipImage();
			// IL.ilOriginFunc(IL.IL_ORIGIN_LOWER_LEFT);
			// IL.ilEnable(IL.IL_ORIGIN_SET);
			// TODO <--------------- flip image!!!
		}

		// align the data
		width = IL.ilGetInteger(IL.IL_IMAGE_WIDTH);
		height = IL.ilGetInteger(IL.IL_IMAGE_HEIGHT);
		widthHW = ImageUtils.getNextPowerOfTwo(width);
		heightHW = ImageUtils.getNextPowerOfTwo(height);
		if (Runtime.getRuntime().freeMemory() <= widthHW * heightHW
				* numberOfBytes) {
			Runtime.getRuntime().gc();
			if (Runtime.getRuntime().freeMemory() <= widthHW * heightHW
					* numberOfBytes) {

				Utility
					.error(
						"<image: "
								+ path
								+ "> the memory is ending.. trying to free memory\n for extra information write on the forum",
						"ImageData(String,boolean)");
				System.out.println("PRE-FREE SPACE: "
						+ (Runtime.getRuntime().freeMemory()) + " -- max: "
						+ (Runtime.getRuntime().maxMemory()) + " -- tot: "
						+ (Runtime.getRuntime().totalMemory()));
				TextureID.FREE_MEMORY = true;
				// TextureID.USE_MIPMAP = true;
				System.setProperty("-Xmx",
					(Runtime.getRuntime().maxMemory() * 2) / 1048576 + "m");
				ImageUtils.freeSpace();

				System.out.println("POST-FREE SPACE: "
						+ (Runtime.getRuntime().freeMemory()) + " -- max: "
						+ (Runtime.getRuntime().maxMemory()) + " -- tot: "
						+ (Runtime.getRuntime().totalMemory()));
			}
		}

		if (widthHW != width || heightHW != height) {
			Utility.error(
				"the image (w:"+width+" h:"+height+") hasn't standard dimension: 2, 4, 8, 16, 32.."
						+ "\n\tyou can remove this Warining using: "
						+ "Utility.ignoreError(stdImage)", "stdImage");
			data = BufferUtils.createByteBuffer(widthHW * heightHW
					* numberOfBytes);
			IL.ilCopyPixels(0, 0, 0, widthHW, heightHW, 1,
				format == ImageType.RGB ? IL.IL_RGB : IL.IL_RGBA, IL.IL_BYTE,
				data);
		} else {
			// data = IL.ilGetData();
			data = BufferUtils.createByteBuffer(widthHW * heightHW
					* numberOfBytes);
			IL.ilCopyPixels(0, 0, 0, widthHW, heightHW, 1,
				format == ImageType.RGB ? IL.IL_RGB : IL.IL_RGBA, IL.IL_BYTE,
				data);
		}

		// ??
		IL.ilDeleteImages(scratch);

	}

	/**
	 * creates a new empty imageData
	 * 
	 * @param width
	 *            the width of imageData
	 * @param height
	 *            the height of imageData
	 * @param useAlpha
	 *            indicates the internal format of image: RGBA or RGB
	 */
	public ImageData(int width, int height, boolean useAlpha) {
		int numberOfBytes = ((format = (useAlpha ? ImageType.RGBA
				: ImageType.RGB)) == ImageType.RGB) ? 3 : 4;
		this.width = width;
		this.height = height;
		this.widthHW = ImageUtils.getNextPowerOfTwo(width);
		this.heightHW = ImageUtils.getNextPowerOfTwo(height);
		data = BufferUtils.createByteBuffer(numberOfBytes * widthHW * heightHW);
	}

	/**
	 * make the image transparent
	 * 
	 * @param red
	 *            the red value (0-255)
	 * @param green
	 *            the green value (0-255)
	 * @param blue
	 *            the blue value (0-255)
	 */
	public void makeTransp(byte red, byte green, byte blue) {
		if (format != ImageType.RGBA) {
			Utility
				.error(
					"if you need make as transparent a texture you need specify to use in alpha-texture: ImageData(\"..\", TRUE )",
					"ImageData.makeTransp(int,int,int)");
			return;
		}
		solid = false;
		if (data == null) {
			Utility.error("the imageData is destroyed",
				"ImageData.makeTransp(int,int,int)");
			return;
		}
		data.rewind();
		int r, g, b;
		int position;
		int step = widthHW * 4; // lenght of a line
		for (int y = 0; y < heightHW; ++y) {
			position = data.position();

			for (int x = 0; x < widthHW; ++x) {
				data.mark();
				r = data.get(); // red
				g = data.get(); // green
				b = data.get(); // blue
				if (r == red && g == green && b == blue) { // test
					data.reset();

					data.put((byte) 0).put((byte) 0).put((byte) 0)
						.put((byte) 0); // transparent
				} else {
					data.get(); // ignore alpha
				}
				if (x == width) { // out image?
					data.position(position + step);
					break; // x = widthHW
				}
			}
			if (y == height) {
				break; // y = heightHW // end
			}
		}
		data.rewind();
	}

	public void setData(BufferedImage image) {
		if (image != null)
			setData(image.getData());
	}

	public void setData(Raster rasterData) {
		if (rasterData == null) {
			Utility.error("rasterData is null", "ImageData.setData(Raster)");
			new Exception().printStackTrace();
			return;
		}
		width = rasterData.getWidth();
		height = rasterData.getHeight();
		if (width > widthHW || height > heightHW) {
			Utility
				.error(
					"width>widthHW or height>heightHW: consult the forum :) -image NOT edited-",
					"ImageData.setData(Raster)");
			new Exception().printStackTrace();
			return;
		}
		int pixelData[] = rasterData.getPixels(0, 0, width, height,
			(int[]) null);
		data.rewind();
		int counter = 0;
		int position = data.position();
		int step = widthHW * 4;
		for (int i = 0; i < pixelData.length; ++i) {
			++counter;
			if (counter > width) { // test X
				data.position(position + step);
				position = data.position();
				counter = 1;
			}
			data.put((byte) pixelData[i]);
			data.put((byte) pixelData[++i]);
			data.put((byte) pixelData[++i]);
			// ++i;
			// data.put((byte)255);
			data.put((byte) pixelData[++i]);
		}
		data.rewind();
		solid = false;
	}

	private void writeObject(java.io.ObjectOutputStream s)
			throws java.io.IOException {
		s.defaultWriteObject();
		data.rewind();
		for (int i = 0; i < widthHW * heightHW; ++i) {
			s.writeByte(data.get());
			s.writeByte(data.get());
			s.writeByte(data.get());
			if (format == ImageType.RGBA)
				s.writeByte(data.get());
		}

	}

	private void readObject(java.io.ObjectInputStream s)
			throws java.io.IOException, ClassNotFoundException {
		s.defaultReadObject();
		int numberOfBytes = format == ImageType.RGB ? 3 : 4;
		data = BufferUtils.createByteBuffer(numberOfBytes * widthHW * heightHW);
		data.rewind();
		for (int i = 0; i < widthHW * heightHW; ++i) {
			data.put(s.readByte()).put(s.readByte()).put(s.readByte());
			if (format == ImageType.RGBA)
				data.put(s.readByte());
		}
		data.rewind();
	}
}
